//The MIT License (MIT)
//
//Copyright (c) 2018 dote
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package com.github.dote.mio.filter;

import com.github.dote.mio.client.IOClient;
import com.github.dote.mio.filter.exception.InvalidSearchKeyException;
import com.github.dote.mio.filter.ext.*;
import com.github.dote.mio.filter.filter.AndCondition;
import com.github.dote.mio.filter.filter.Condition;
import com.github.dote.mio.filter.filter.OrCondition;
import com.github.dote.mio.filter.filter.SearchFilter;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class QueryImpl implements IQuery {

    private Collection<IOClient> clientList;

    public QueryImpl(Collection<IOClient> clientList) {
        this.clientList = clientList;
    }

    @Override
    public List<IOClient> findAll(Sort sort) {
        return findBySort(this.clientList, sort);
    }

    private List<IOClient> findBySort(Collection<IOClient> srcList, Sort sort) {
        if (sort == null) {
            return new ArrayList<>(srcList);
        }
        List<IOClient> cpList = new ArrayList<>(srcList);
        Comparator<IOClient> comparator=createSortComparator(sort.getOrder());
        try{
            cpList.sort(comparator);
        }catch (Exception e){
            throw new InvalidSearchKeyException(sort.getOrder().getProperty(),e);
        }
        return cpList;
    }



    /**
     * 构建排序compartor
     *
     * @param order
     * @return
     */
    private Comparator<IOClient> createSortComparator( Sort.Order order) {
        Function<IOClient, Integer> func = c -> c.getAttr(order.getProperty());
        Comparator<IOClient> comparator = null;
        comparator = Comparator.comparing(func);
        if (order.getDirection() == Sort.Direction.DESC) {
            comparator = comparator.reversed();
        }
        return comparator;
    }

    @Override
    public Page<IOClient> findAll(Pageable pageable) {
        return findByPage(clientList, pageable);
    }

    private Page<IOClient> findByPage(Collection<IOClient> srcList, Pageable pageable) {
        List<IOClient> clientList = findBySort(srcList, pageable.getSort());
        List<IOClient> resultList = new ArrayList<>();
        int fromIdx = (pageable.getPageNumber() - 1) * pageable.getPageSize();
        int toIdx = fromIdx + pageable.getPageSize();

        if (clientList != null && clientList.size() > 0) {
            if (clientList.size() < toIdx) {
                toIdx = clientList.size();
            }
            if (fromIdx < toIdx) {
                resultList.addAll(clientList.subList(fromIdx, toIdx));
            }
        }

        Page<IOClient> page = new PageImpl<IOClient>(resultList, pageable, clientList.size());
        return page;
    }

    @Override
    public Page<IOClient> findAll(Searchable searchable) {
        return new PageImpl<IOClient>(execQuery(searchable, true), searchable.getPage(), clientList.size());
    }

    @Override
    public long count(Searchable searchable) {
        return execQuery(searchable, false).size();
    }

    /**
     * 执行查询
     *
     * @param search
     * @return
     */
    public List<IOClient> execQuery(Searchable search, boolean usePageAndSort) {
        if (!search.hasSearchFilter()) {
            return new ArrayList<>();
        }
        Stream<IOClient> stream = clientList.stream();
        for (SearchFilter searchFilter : search.getSearchFilters()) {

            if (searchFilter instanceof Condition) {
                Condition condition = (Condition) searchFilter;
                if (condition.getOperator() == SearchOperator.custom) {
                    continue;
                }
            }
            stream = genCondition(stream, searchFilter);
        }
        List<IOClient> resList = stream.collect(Collectors.toList());
        if (usePageAndSort) {
            if (search.getPage() != null) {
                return findByPage(resList, search.getPage()).getContent();
            } else {
                if (search.getSort() != null) {
                    return findBySort(resList, search.getSort());
                }
            }
        }
        return resList;
    }

    /**
     * 条件过滤
     *
     * @param stream
     * @param searchFilter
     * @return
     */
    private Stream<IOClient> genCondition(Stream<IOClient> stream, SearchFilter searchFilter) {
        Stream nStream = null;
        if (searchFilter instanceof Condition) {
            Condition condition = (Condition) searchFilter;

            switch (condition.getOperator()) {
                case eq:
                    nStream = stream.filter((c) -> condition.getValue().equals(c.getAttr(condition.getSearchProperty())));
                    break;
                case ne:
                    nStream = stream.filter((c) -> !condition.getValue().equals(c.getAttr(condition.getSearchProperty())));
                    break;
                case in:
                    nStream = stream.filter((c) -> c.hasAttr(condition.getSearchProperty()) && condition.getValue() instanceof Collection && ((Collection) (condition.getValue())).contains(c.getAttr(condition.getSearchProperty())));
                    break;
                case notIn:
                    nStream = stream.filter((c) -> !c.hasAttr(condition.getSearchProperty()) || (condition.getValue() instanceof Collection && !((Collection) (condition.getValue())).contains(c.getAttr(condition.getSearchProperty()))));
                    break;
                case isNull:
                    nStream = stream.filter((c) -> !c.hasAttr(condition.getSearchProperty()));
                    break;
                case isNotNull:
                    nStream = stream.filter((c) -> c.hasAttr(condition.getSearchProperty()));
                    break;
                case lt:
                    nStream = stream.filter((c) -> c.hasAttr(condition.getSearchProperty()) && condition.getValue() instanceof Integer && Integer.parseInt(condition.getValue().toString()) > (Integer) c.getAttr(condition.getSearchProperty()));
                    break;
                case lte:
                    nStream = stream.filter((c) -> c.hasAttr(condition.getSearchProperty()) && condition.getValue() instanceof Integer && Integer.parseInt(condition.getValue().toString()) >= (Integer) c.getAttr(condition.getSearchProperty()));
                    break;
                case gt:
                    nStream = stream.filter((c) -> c.hasAttr(condition.getSearchProperty()) && condition.getValue() instanceof Integer && Integer.parseInt(condition.getValue().toString()) < (Integer) c.getAttr(condition.getSearchProperty()));
                    break;
                case gte:
                    nStream = stream.filter((c) -> c.hasAttr(condition.getSearchProperty()) && condition.getValue() instanceof Integer && Integer.parseInt(condition.getValue().toString()) <= (Integer) c.getAttr(condition.getSearchProperty()));
                    break;
            }
        } else if (searchFilter instanceof OrCondition) {
            List<Stream<IOClient>> streamList = new ArrayList<>();
            for (SearchFilter orSearchFilter : ((OrCondition) searchFilter).getOrFilters()) {
                Stream<IOClient> orStream = genCondition(clientList.stream(), orSearchFilter);
                streamList.add(orStream);
            }


            List<IOClient> resList = new ArrayList<>();
            for (Stream<IOClient> orS : streamList) {
                resList = union(resList, orS.collect(Collectors.toList()));
            }
            nStream = resList.stream();
        } else if (searchFilter instanceof AndCondition) {
            for (SearchFilter andSearchFilter : ((AndCondition) searchFilter).getAndFilters()) {
                nStream = genCondition(clientList.stream(), andSearchFilter);
            }
        }
        return nStream;
    }

    private List union(List list1, List list2) {
        list2.removeAll(list1);
        list1.addAll(list2);
        return list1;
    }

}
